﻿using System.Security.Cryptography;
using System.Text;

namespace ECommon
{
    /// <summary>
    /// md5工具
    /// </summary>
    public static class Md5Tool
    {
        /// <summary>
        /// 计算大文件的32位小写md5值
        /// </summary>
        /// <param name="fileFullPath">大文件绝对路径</param>
        /// <returns>32位小写md5值</returns>
        /// <exception cref="ArgumentNullException">参数为空异常</exception>
        /// <exception cref="ArgumentException">变量错误异常</exception>
        public static string GetMd5ByBigFile(string fileFullPath)
        {
            if (!ValidFile(fileFullPath))
            {
                throw new ArgumentNullException("fileFullPath", "文件路径为空或文件不存在");
            }
            //缓冲大小
            int bufferSize = 2 * 1024 * 1024;
            byte[] buffer = new byte[bufferSize];
            using (Stream inputStream = File.Open(fileFullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using (HashAlgorithm hashAlgorithm = SHA1.Create())
                {
                    //循环读写文件
                    byte[] output = new byte[bufferSize];
                    int readLength = inputStream.Read(buffer, 0, bufferSize);
                    while (readLength > 0)
                    {
                        //开始计算
                        hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
                        readLength = inputStream.Read(buffer, 0, bufferSize);
                    }
                    //完成计算
                    hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
                    if (hashAlgorithm.Hash == null)
                    {
                        throw new ArgumentException("文件散列变量数组出错");
                    }
                    return BitConverter.ToString(hashAlgorithm.Hash).Replace("-", "");
                }
            }
        }

        /// <summary>
        /// 计算小文件的32位小写md5值
        /// </summary>
        /// <param name="fileFullPath">小文件绝对路径</param>
        /// <returns>32位小写md5值</returns>
        /// <exception cref="ArgumentNullException">参数为空异常</exception>
        /// <exception cref="Exception">文件过大异常</exception>
        public static string GetMd5BySmallFile(string fileFullPath)
        {
            if (!ValidFile(fileFullPath))
            {
                throw new ArgumentNullException("fileFullPath", "文件路径为空或文件不存在");
            }
            using (FileStream fs = File.Open(fileFullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                if (fs.Length > 1024 * 1024 * 10)
                {
                    throw new Exception("文件过大，请使用API：“GetMd5ByBigFile”。");
                }
                MD5 md5 = MD5.Create();
                return GetStringFromByteArray(md5.ComputeHash(fs));
            }
        }

        /// <summary>
        /// 计算字符串的md5值
        /// </summary>
        /// <param name="utf8String">utf-8编码的字符串</param>
        /// <returns>32位小写md5值</returns>
        public static string GetMd5ByString(string utf8String)
        {
            if (string.IsNullOrWhiteSpace(utf8String))
            {
                return utf8String;
            }
            MD5 md5 = MD5.Create();
            return GetStringFromByteArray(md5.ComputeHash(Encoding.UTF8.GetBytes(utf8String)));
        }

        /// <summary>
        /// 根据当前时间获取md5
        /// </summary>
        /// <returns>32位小写md5值</returns>
        public static string GetMd5ByTimeTick()
        {
            return GetMd5ByString(DateTime.Now.Ticks.ToString());
        }

        /// <summary>
        /// 字节数组转换为字符串
        /// </summary>
        /// <param name="data">字节数组</param>
        /// <returns>转换后的字符串</returns>
        /// <exception cref="ArgumentNullException">参数为空异常</exception>
        private static string GetStringFromByteArray(byte[] data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data", "字节数据文件为空");
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sb.Append(data[i].ToString("x2"));
            }
            return sb.ToString().ToLower();
        }

        /// <summary>
        /// 验证文件是否存在
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <returns>验证结果</returns>
        /// <exception cref="ArgumentNullException">参数为空异常</exception>
        /// <exception cref="FileNotFoundException">文件不存在异常</exception>
        private static bool ValidFile(string filePath)
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                throw new ArgumentNullException("filePath", "文件路径为空");
            }
            filePath = filePath.Replace('\\', Path.DirectorySeparatorChar);
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(string.Format("文件：{0}不存在", filePath), Path.GetFileName(filePath));
            }
            return true;
        }
    }
}